package com.codeyang.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 描述:
 *
 * @author CodeYang_Site
 * @version 2021/5/18 13:02
 */
@Component
public class JWTCheckFilter extends OncePerRequestFilter {

    /**
     * 集成Redis 把jwt存储在远端 这样保证jwt的状态,
     * 1.这样前端修改 载体后,即使是以同样的签名加密出来jwt 与远端redis存储的不符合,那么就认证不成功
     * 达到了后端校验jwt的目的
     * 2.存入redis
     * 3.放入securityContextHolder
     */
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 解析JWT 接管所有请求的请求入口
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setContentType("application/json;charset=utf-8");
        // 登录的要放行掉 -后面可以扩展为白名单
        String path = request.getRequestURI();
        String method = request.getMethod();
        if ("/doLogin".equalsIgnoreCase(path) && method.equalsIgnoreCase("POST")) {
            // 登录 放行
            filterChain.doFilter(request, response);
            return;
        }
        //不放行的就是要校验jwt jwt规定放在header中
        String authorization = request.getHeader("Authorization");
        if (StringUtils.hasText(authorization)) {
            //截取前缀不要,因为一般 Authorization 都是 bearer <jwtxxxxx>
            //bearer持有人 一般格式为 bearer<jwt>
            String jwt = authorization.replaceAll("bearer<", "").replaceAll(">", "");
            //双重校验,保证修改修改了 body但是我们无法直接校验body ,使用redis来判断之前 存储进去的str ,这样修改后的str肯定要发省变化,必然不通过
            //这样就保证了jwt的状态性
            //jwt的不可修改状态.
            if (StringUtils.hasText(jwt) && redisTemplate.hasKey("jwt:token:" + jwt)) {

                //通过了redis校验,解析jwt 这样签名不同也无法通过
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("SECURITY-JWT-KEY")).build();
                try {
                    DecodedJWT verify = jwtVerifier.verify(jwt);
                    // 把用户信息转出来
                    Claim usernameClaim = verify.getClaim("username");
                    String username = usernameClaim.asString();
                    //权限 在转换出来
                    Claim authsClaim = verify.getClaim("auths");
                    List<String> auths = authsClaim.asList(String.class);
                    //在将权限转换为 security认识的权限
                    ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>(auths.size());
                    auths.forEach(auth -> grantedAuthorities.add(new SimpleGrantedAuthority(auth)));

                    //设置到上下文当中去,相当于就是登录了
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);
                    //存放一些ip细节之类的
                    authenticationToken.setDetails(new WebAuthenticationDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    //放行
                    filterChain.doFilter(request, response);
                    return;

                } catch (JWTVerificationException e) {
                    //解析失败,返回错误
                    PrintWriter writer = response.getWriter();
                    HashMap<String, Object> map = new HashMap<>(4);
                    map.put("code", 401);
                    map.put("msg", "JWT解析失败,请勿改变网站登录流程");
                    ObjectMapper objectMapper = new ObjectMapper();
                    String s = objectMapper.writeValueAsString(map);
                    writer.write(s);
                    writer.flush();
                    writer.close();
                }
            }
        }
        // 没有jwt
        // 解析失败 返回错误信息
        PrintWriter writer = response.getWriter();
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("code", 401);
        map.put("msg", "未授权");
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(map);
        writer.write(s);
        writer.flush();
        writer.close();
    }
}
